#!/usr/bin/env ruby

LKP_SRC = ENV['LKP_SRC'] || File.dirname(File.dirname(File.realpath($PROGRAM_NAME)))

require "#{LKP_SRC}/lib/statistics"
require "#{LKP_SRC}/lib/string_ext"
require "#{LKP_SRC}/lib/array_ext"
require "#{LKP_SRC}/lib/tests/stats"

stats = LKP::Stats.new

class Stater
  def initialize(test, test_script)
    @test = test
    @test_script = test_script

    @test_prefix = "#{@test}.#{@test_script}"
  end

  def stat(line, stats)
    if line =~ /^# (ok|not ok) \d+ (.*)/
      # some tests under tools/testing/selftests use same result format:
      # selftests: seccomp: seccomp_bpf
      # ok 1 global.kcmp
      if %w{cgroup seccomp sigaltstack syscall_user_dispatch core landlock}.include? @test
        result = $1
        test_case = $2

        if test_case =~ /SKIP/
          # selftests: cgroup: test_stress.sh
          # ok 4 # SKIP test_cgcore_no_internal_process_constraint_on_threads
          test_case = "#{@test_prefix}.#{test_case.split(' SKIP ').last}"
          stats.add_test_result line, test_case, 'skip' unless stats.key? test_case
        else
          # cgroup will repeatedly output below subtests, ignore the duplicates.
          # selftests: cgroup: test_kmem
          # not ok 1 test_kmem_basic
          # not ok 1 test_kmem_basic
          # not ok 1 test_kmem_basic
          test_case = "#{@test_prefix}.#{test_case}"
          stats.add_test_result line, test_case, result unless stats.key? test_case
        end
      end

      return
    end

    case line
    when /^(ok|not ok).*selftests: (\S*): (\S*)( # SKIP)?( \[)?/,
         /^Test \d+ (ok|not ok) - selftests: (\S*): (\S*)( # SKIP)?( \[)?/
      if $4
        # make: Entering directory .*/android'
        # not ok 1 selftests: android: run.sh # SKIP

        # selftests: pstore: pstore_tests
        # not ok 2 selftests: pstore: pstore_post_reboot_tests # SKIP

        # add "duplicate key" check to avoid below duplication
        # 1908 ok 1 selftests: lkdtm: PANIC.sh # SKIP
        # 3109 ok 1 selftests: lkdtm: PANIC.sh # SKIP
        # 9951 ok 1 selftests: lkdtm: PANIC.sh # SKIP

        # Test 6 ok - selftests: cgroup: test_cpu # SKIP
        stats.add_test_result line, "#{@test}.#{$3}", 'skip' unless stats.key? "#{@test}.#{$3}"
      else
        # not ok 46 selftests: net: vrf_route_leaking.sh # exit=1
        # ok 1 selftests: vm: run_vmtests
        # ok 1 selftests: memory-hotplug: mem-on-off-test.sh
        # ok 1..1 selftests: capabilities: test_execve [PASS]

        # add "duplicate key" check to avoid below duplication
        # 2618 ok 2 selftests: lkdtm: BUG.sh # SKIP
        # 7668 ok 2 selftests: lkdtm: BUG.sh # SKIP

        # Test 1 ok - selftests: x86: single_step_syscall_32
        stats.add_test_result line, "#{@test}.#{$3}", $1 unless stats.key? "#{@test}.#{$3}"
      end
    when /: recipe for target.+failed$/, /^make: \*\*\* (.*) (Error \d+|Stop\.)$/
      # Makefile:47: recipe for target 'sysret_ss_attrs_64' failed
      # make: *** No rule to make target .*, needed by 'all'.  Stop.
      stats.add_test_result line, @test.to_s, 'make_fail' unless stats.key? @test.to_s
    when /^*selftests:\s*(\S*) (\[|\[ )(PASS|FAIL|SKIP)/
      # selftests: mpx-mini-test_64 [PASS]

      # ignore below '[PASS]' to avoid duplication
      # selftests: bpf: test_xdp_vlan_mode_generic.sh
      # selftests: xdp_vlan_mode_generic [PASS]
      # ok 35 selftests: bpf: test_xdp_vlan_mode_generic.sh

      # ignore detail stats of futex to avoid duplication
      # c0e64368308a ("stats/kernel-selftests: rm detail stats for futex")
      stats.add_test_result line, "#{@test}.#{$1}", $3 unless %w(bpf futex).include? @test
    when %r{make: Leaving directory .*/(.*)'}
      @test = @test_script = @test_prefix = @test_case = @test_subcase = nil
    end
  end
end

class NetStater < Stater
  def stat(line, stats)
    case line
    # begain for net.fcnal-test.sh
    when /(IPv4 ping|IPv4\/TCP|IPv4\/UDP|IPv4 Netfilter)/,
         /(IPv6 ping|IPv6\/TCP|IPv6\/UDP|IPv6 Netfilter)/,
         /(IPv6 address binds|IPv4 address binds)/,
         /(OUTPUT tests|INPUT tests)/,
         /(Use cases)/,
         /(Run time tests - ipv4)/
      # ##########################################################################
      # IPv4 ping
      # ##########################################################################
      #
      #
      # ##########################################################################
      # No VRF
      #
      # SYSCTL: net.ipv4.raw_l3mdev_accept=0
      #
      # TEST: ping out - ns-B IP                                        [ OK ]

      # ###########################################################################
      # Run time tests - ipv4
      # ###########################################################################
      #
      # TEST: Device delete with active traffic - ping in - ns-A IP     [ OK ]
      @test_case = $1
      @test_subcase1 = @test_subcase2 = nil if @test_case =~ /Run time tests - ipv(4|6)/
    when /^# (No VRF|With VRF)/,
         /(Device enslaved to bridge|Ping LLA with multiple interfaces)/,
         /(TCP reset|ICMP unreachable)/
      # ###########################################################################
      # Use cases
      # ###########################################################################
      #
      #
      # ###########################################################################
      # Device enslaved to bridge
      #
      # TEST: Bridge into VRF - IPv4 ping out                           [ OK ]
      @test_subcase1 = $1
      @test_subcase2 = nil if @test_subcase1 =~ /Device enslaved to bridge|Ping LLA with multiple interfaces
                                                 TCP reset|ICMP unreachable/

      # start a new @test_subcase1, thus reset @sysctl
      @sysctl = false
    when /^# SYSCTL: (.*)/
      # there may be indefinite num of 'SYSCTL' like below,
      # here we use @sysctl flag to combine all 'SYSCTL'.
      # SYSCTL: net.ipv4.raw_l3mdev_accept=0
      #
      # TEST: ping out - ns-B IPv6                                    [ OK ]
      # ...
      # SYSCTL: net.ipv4.ping_group_range=0 2147483647
      #
      # SYSCTL: net.ipv4.raw_l3mdev_accept=0
      #
      # TEST: ping out - ns-B IPv6                                    [ OK ]
      @test_subcase2 = if !@sysctl
                         # net.ipv4.raw_l3mdev_accept=0
                         $1
                       else
                         # net.ipv4.ping_group_range=0_2147483647.net.ipv4.raw_l3mdev_accept=0
                         "#{@test_subcase2}.#{$1}"
                       end
      # here we met one 'SYSCTL', thus set below flag to true
      @sysctl = true
    # end for net.fcnal-test.sh
    when /^# TEST SECTION: (.*)/
      # selftests: net: fib-onlink-tests.sh
      # TEST SECTION: IPv4 onlink
      @test_case = $1
    when /^# TEST SUBSECTION: (.*)/
      # selftests: net: fib-onlink-tests.sh
      # TEST SUBSECTION: Valid onlink commands
      @test_subcase = $1
    when /#     (.*tart point)/,
         /#     (.*device deleted)/,
         /#     (Route deleted on down)/,
         /#     (.*device.* down.*)/,
         /#     (.*arrier)/
      #     Start point
      #     Verify start point
      #     One nexthop device deleted
      #     One device down, one up
      #     Both devices down
      #     Carrier off on nexthop
      #     Route to local address with carrier down

      # selftests: net: fib_tests.sh
      @test_subcase = $1
    when /^# (.*qdisc on VRF device)/
      # selftests: net: vrf-xfrm-tests.sh
      # No qdisc on VRF device
      # netem qdisc on VRF device
      @test_case = $1
    when /^# (Single|Multipath|Single|Admin|Local|Single|FIB|IPv4|IPv6|Basic|Legacy|Routing) (.*)/
      # selftests: net: icmp_redirect.sh
      # Routing with nexthop objects and VRF
      @test_case = "#{$1} #{$2}" if %w(icmp_redirect.sh fib_tests.sh fib_nexthops.sh vrf_route_leaking.sh).include? @test_script

      # empty @test_subcase when get new @test_case
      @test_subcase = nil if @test_script == 'fib_tests.sh'
    when /^#\s+(PASS|SKIP|FAIL): (.*)/
      if @test_case
        # selftests: net: vrf_route_leaking.sh
        #
        # ###########################################################################
        # IPv4 (sym route): VRF ICMP error route lookup traceroute
        # ###########################################################################
        #
        # SKIP: Could not run IPV4 test without traceroute
        stats.add_test_result line, "#{@test_prefix}.#{@test_case}.#{$2}", $1
      else
        # selftests: net: netdevice.sh
        # SKIP: eth0: interface already up
        @result = $1
        @test_case = $2

        if @test_case == 'fdb get tests: iproute2 too old'
          # below rtnetlink.sh's subtest will repeate twice thus cause duplication
          # SKIP: fdb get tests: iproute2 too old
          # and it's passed stat will be
          # PASS: bridge fdb get
          stats.add_test_result line, "#{@test_prefix}.bridge_fdb_get", @result unless stats.key? "#{@test_prefix}.bridge_fdb_get"
        else
          stats.add_test_result line, "#{@test_prefix}.#{@test_case}", $1
        end
        @test_case = nil
        @result = nil
      end
    when /^# \[\s*(OK|FAIL|SKIP)\s*\]\s+(.*)/
      # selftests: net: tls
      # [       OK ] tls_basic.base_base
      # [       OK ] tls.sendfile

      # ignore below duplication
      # selftests: net: ipv6_flowlabel.sh
      # TEST management
      # [OK]   flowlabel_get(fd, 1, 255, 1)
      # [OK]   flowlabel_get(fd, 1, 255, 0)
      # [OK]   flowlabel_get(fd, 1, 255, 1)
      stats.add_test_result line, "#{@test_prefix}.#{$2}", $1 unless stats.key? "#{@test_prefix}.#{$2}"
    when /^#     TEST: (.*) \[ ?(OK|FAIL|SKIP) ?\]$/
      if @test_subcase
        # selftests: net: fib-onlink-tests.sh
        # ######################################################################
        # TEST SECTION: IPv4 onlink
        # ######################################################################
        #
        # #########################################
        # TEST SUBSECTION: Valid onlink commands
        #
        # #########################################
        # TEST SUBSECTION: default VRF - main table
        #     TEST: unicast connected                                   [ OK ]
        stats.add_test_result line, "#{@test_prefix}.#{@test_case}.#{@test_subcase}.#{$1}", $2
      else
        # selftests: net: fib_rule_tests.sh
        #
        # ######################################################################
        # TEST SECTION: IPv4 fib rule
        # ######################################################################
        #
        #     TEST: rule4 check: oif dummy0                             [ OK ]

        # ignore some detail stats of fib_tests.sh to avoid duplication
        # selftests: net: fib_tests.sh
        #
        # IPv4 route with IPv6 gateway tests
        #    TEST:     Multipath route delete exact match                        [ OK ]
        #    TEST: Multipath route add - v4 nexthop then v6                      [ OK ]
        #    TEST:     Multipath route delete - nexthops in wrong order          [ OK ]
        #    TEST:     Multipath route delete exact match                        [ OK ]
        stats.add_test_result line, "#{@test_prefix}.#{@test_case}.#{$1}", $2 unless @test_case == 'IPv4 route with IPv6 gateway tests' || stats.key?("#{@test_prefix}.#{@test_case}.#{$1}")
      end
    when /^# TEST: (.*) \[ ?(PASS|OK|FAIL|SKIP) ?\]/
      # this 'TEST' line means current 'SYSCTL' is done,
      # and next time we capture 'SYSCTL' will be a new one.
      @sysctl = false

      # ##########################################################################
      # IPv4 ping
      # ##########################################################################
      #
      #
      # ##########################################################################
      # No VRF
      #
      # SYSCTL: net.ipv4.raw_l3mdev_accept=0
      #
      # TEST: ping out - ns-B IP                                        [ OK ]

      # @test_case = IPv4 ping
      # @test_subcase1 = No VRF
      # @test_subcase2 = SYSCTL: ne.ipv4.raw_l3mdev_accept=0
      # @test_subcase3 = TEST: ping out - ns-B IP
      # @subcase_stat = OK
      @test_subcase3 = $1
      @subcase_stat = $2
      if @test_subcase2
        # ###########################################################################
        # IPv4 ping
        # ###########################################################################
        #
        #
        # #################################################################
        # No VRF
        #
        # SYSCTL: net.ipv4.raw_l3mdev_accept=0
        #
        # TEST: ping out - ns-B IP                                      [ OK ]
        stats.add_test_result line, "#{@test_prefix}.#{@test_case}.#{@test_subcase1}.#{@test_subcase2}.#{@test_subcase3}", @subcase_stat if @test_script == 'fcnal-test.sh'
      elsif @test_subcase1
        # ###########################################################################
        # Use cases
        # ###########################################################################
        #
        #
        # #################################################################
        # Device enslaved to bridge
        #
        # TEST: Bridge into VRF - IPv4 ping out                         [ OK ]
        stats.add_test_result line, "#{@test_prefix}.#{@test_case}.#{@test_subcase1}.#{@test_subcase3}", @subcase_stat if @test_script == 'fcnal-test.sh'
      elsif @test_case
        # selftests: net: vrf_route_leaking.sh
        #
        # ###########################################################################
        # IPv4 (sym route): VRF ICMP ttl error route lookup ping
        # ###########################################################################
        #
        # TEST: Basic IPv4 connectivity                                      [ OK ]

        # ignore detail stats of fib_nexthops.sh to avoid duplication
        # selftests: net: fib_nexthops.sh
        # IPv4 groups functional
        # ----------------------
        # TEST: IPv6 nexthop with IPv4 route                                  [ OK ]
        # TEST: IPv6 nexthop with IPv4 route                                  [ OK ]
        # IPv4 functional runtime
        # TEST: IPv6 nexthop with IPv4 route                                  [ OK ]
        # TEST: IPv4 route with mixed v4-v6 multipath route                   [ OK ]
        # TEST: IPv6 nexthop with IPv4 route                                  [ OK ]
        if @test_prefix == 'net.ioam6.sh'
          # Here we use the mode of '11' as the mode of '12' to avoid duplication
          # TEST: Trace type with bit 11 only (inline mode)                     [ OK ]
          # TEST: Trace type with bit 12 only                                   [ OK ]
          # ...
          # TEST: Trace type with bit 11 only (encap mode)                      [ OK ]
          # TEST: Trace type with bit 12 only                                   [ OK ]
          if @test_subcase3 =~ /(inline mode|encap mode)/
            stats.add_test_result line, "#{@test_prefix}.#{@test_case}.#{@test_subcase3}", @subcase_stat
            @old_mode = $1
          else
            stats.add_test_result line, "#{@test_prefix}.#{@test_case}.#{@test_subcase3} (#{@old_mode})", @subcase_stat
          end
        else
          stats.add_test_result line, "#{@test_prefix}.#{@test_case}.#{@test_subcase3}", @subcase_stat unless %w(fib_nexthops.sh).include? @test_script
        end
      else
        # selftests: net: pmtu.sh
        # TEST: ipv4: PMTU exceptions                                        [ OK ]

        # ignore below duplication
        # selftests: net: fib_nexthop_multiprefix.sh
        # TEST: IPv4: host 0 to host 1, mtu 1300                              [ OK ]
        # TEST: IPv6: host 0 to host 1, mtu 1300                              [FAIL]
        #
        # TEST: IPv4: host 0 to host 2, mtu 1350                              [ OK ]
        # TEST: IPv6: host 0 to host 2, mtu 1350                              [FAIL]
        #
        # TEST: IPv4: host 0 to host 3, mtu 1400                              [ OK ]
        # TEST: IPv6: host 0 to host 3, mtu 1400                              [FAIL]
        #
        # TEST: IPv4: host 0 to host 1, mtu 1300                              [ OK ]
        # TEST: IPv6: host 0 to host 1, mtu 1300                              [FAIL]
        test_name = "#{@test_prefix}.#{@test_subcase3}"
        stats.add_test_result line, test_name, @subcase_stat unless stats.key?(test_name)
      end
    when /^# (UDP|TCP|DCCP) (.*) \.\.\. (pass|fail|skip)/
      # selftests: net: reuseport_addr_any.sh
      # UDP IPv4 ... pass
      stats.add_test_result line, "#{@test_prefix}.#{$1} #{$2}", $3
    when /RUN\s+([^\s]+)/
      # #  RUN           global.tls_v6ops ...
      @test_case = $1
    when /^# (ok|fail|skip|not ok) \d+ ([^#].*)/
      # selftests: net: reuseaddr_ports_exhausted.sh
      # ok 1 global.reuseaddr_ports_exhausted_unreusable
      if $2 != @test_case
        log_warn "#{$2} not equals to #{@test_case}"
        exit 1
      end

      stats.add_test_result line, "#{@test_prefix}.#{$2}", $1
      @test_case = nil
    when /^# ok \d+ # SKIP (.*)/
      # ok 22 # SKIP no TLS support
      stats.add_test_result line, "#{@test_prefix}.#{@test_case}", 'skip'
      @test_case = nil
    when /^# (running|run) ([^\s]+) test/,
         /^# (ipv\d [^:]+)$/,
         /^# (dgram.*|raw.*)$/
      # selftests: net: run_afpackettests
      # --------------------
      # running psock_fanout test
      # --------------------
      # [PASS]

      # selftests: net: udpgso.sh
      # ipv4 cmsg
      # OK

      # selftests: net: msg_zerocopy.sh
      # ipv4 tcp -t 1
      # ok

      # selftests: net: ip_defrag.sh
      # ipv4 defrag
      # PASS

      # selftests: net: psock_snd.sh
      # dgram
      # OK
      @test_case = $2 || $1
    when /^# \[(PASS|FAIL|SKIP)\]$/,
         /^# (OK|ok|PASS)$/
      stats.add_test_result line, "#{@test_prefix}.#{@test_case}", $1 if @test_case
    when /^#\s(.*)\[\s*(OK|FAIL|SKIP)\s*\]/
      # selftests: net: test_vxlan_under_vrf.sh
      # Checking HV connectivity                                           [ OK ]
      stats.add_test_result line, "#{@test_prefix}.#{$1}", $2
    when /^# (ipv4|ipv6)$/
      # selftests: net: udpgro.sh
      # ipv4
      #  no GRO                                  ok
      @test_case = $1
    when /#  ([a-zA-Z].*)(ok|fail)/
      stats.add_test_result line, "#{@test_prefix}.#{@test_case}.#{$1}", $2
    when /^# (\w.*)(ok $|fail )/
      # selftests: net: veth.sh
      # default - gro flag                                           ok
      #         - peer gro flag                                      ok
      # default channels                                             fail rx:1:1 tx:1:1 combined:n/a:0
      test_name = "#{@test_prefix}.#{$1.split('-')[0]}"

      # ignore below duplication
      # 6404 # setting invalid channels nr                                  ok
      # 6408 # setting invalid channels nr                                  ok
      stats.add_test_result line, test_name, $2 unless stats.key?(test_name)
    else
      super(line, stats)
    end
  end
end

class VmStater < Stater
  def stat(line, stats)
    case line
    when /^#\s+running\s+(.+)/
      # running hugepage-shm
      @test_case = $1
    when /^#\s+running: (\S+)(.+)# (\S+)/
      # running: gup_test -u # get_user_pages_fast() benchmark
      # vm.run_vmtests.sh.gup_test.get_user_pages_fast.pass: 1
      @test_case = "#{$1}.#{$3}"
    when /^#\s+\[(PASS|FAIL)\]/, /^#\s+LKP (SKIP)/
      return unless @test_case

      test_name = "#{@test_prefix}.#{@test_case}".tr('-', '_')
      stats.add_test_result line, test_name, $1 unless stats.key?(test_name)
      @test_case = nil
    when /^# ([a-zA-Z].*): [0-9a-z]+ - (OK|FAIL)/
      # mmap(HIGH_ADDR, MAP_FIXED): 0xffffffffffffffff - FAILED
      test_name = "#{@test_prefix}.#{@test_case}.#{$1}"

      # treat below subtests as same
      # mmap(ADDR_SWITCH_HINT - PAGE_SIZE, PAGE_SIZE): 0x7fe8ef2a4000 - OK
      # mmap(ADDR_SWITCH_HINT - PAGE_SIZE, PAGE_SIZE): 0x7fe8ef29d000 - OK
      stats.add_test_result line, test_name, $2 unless stats.key?(test_name)
    when /^# # \[RUN\]\s(.*)/
      # selftests: vm: madv_populate
      # # [RUN] test_prot_read
      # ok 1 MADV_POPULATE_READ with PROT_READ
      @test_subcase = $1
    when /^# (ok|not ok) \d+\s(# SKIP |)(.*)/
      # if it has '# # [RUN]' level

      test_name = if @test_case && @test_subcase
                    # selftests: vm: run_vmtests.sh
                    # -----------------------
                    # running ./madv_populate
                    # -----------------------
                    # TAP version 13
                    # 1..21
                    # # [RUN] test_prot_read
                    # ok 1 MADV_POPULATE_READ with PROT_READ
                    "#{@test_prefix}.#{@test_case}.#{@test_subcase}.#{$3}"
                  elsif @test_case && !@test_subcase
                    # running ./memfd_secret
                    # ----------------------
                    # page_size: 4096, mlock.soft: 8388608, mlock.hard: 8388608
                    # TAP version 13
                    # 1..4
                    # ok 2 # SKIP memfd_secret is not supported
                    "#{@test_prefix}.#{@test_case}.#{$3}"
                  elsif !@test_case && @test_subcase
                    # selftests: vm: madv_populate
                    # TAP version 13
                    # 1..21
                    # # [RUN] test_prot_read
                    # ok 1 MADV_POPULATE_READ with PROT_READ
                    "#{@test_prefix}.#{@test_subcase}.#{$3}"
                  else
                    # selftests: vm: soft-dirty
                    # TAP version 13
                    # 1..5
                    # ok 1 Test test_simple
                    "#{@test_prefix}.#{$3}"
                  end

      test_result = $2['SKIP'] ? 'skip' : $1

      # ignore below duplication
      # # [RUN] test_softdirty
      # ok 17 range is not softdirty
      # ok 19 range is not softdirty
      stats.add_test_result line, test_name, test_result unless stats.key?(test_name)
    else
      super(line, stats)
    end
  end
end

class MemoryHotplugStater < Stater
  def stat(line, stats)
    case line
    when /^selftests: memory-hotplug \[FAIL\]/
      # selftests: memory-hotplug [FAIL]
      stats.add_test_result line, @test_prefix.to_s, 'fail'
      @test_script = nil
    when %r{make: Leaving directory .*/(.*)'}
      # do not add stats here if it has below 2 lines
      # ok 1 selftests: memory-hotplug: mem-on-off-test.sh
      # selftests: memory-hotplug [FAIL]
      stats.add_test_result line, @test_prefix.to_s, 'pass' unless stats.key?(@test_prefix.to_s) || !@test_script
    when %r{: recipe for target.+failed}
      # Makefile:47: recipe for target 'sysret_ss_attrs_64' failed
      stats.add_test_result line, @test.to_s, 'make_fail'
    else
      super(line, stats)
    end
  end
end

# for kernel < v4.18-rc1
class MountStater < Stater
  def stat(line, stats)
    case line
    when /^WARN: No \/proc\/self\/uid_map exist, test skipped/
      # WARN: No /proc/self/uid_map exist, test skipped.
      stats.add_test_result line, @test_prefix.to_s, 'skip'
      @test_script = nil
    when /(^(MS.+|Default.+) malfunctions$)|(^Mount flags unexpectedly changed after remount$)/
      # Mount flags unexpectedly changed after remount
      stats.add_test_result line, @test_prefix.to_s, 'fail'
      @test_script = nil
    when %r{make: Leaving directory .*/(.*)'}
      # test pass if it's not skip or fail
      stats.add_test_result line, @test_prefix.to_s, 'pass' if @test_script
    end
  end
end

class X86Stater < Stater
  def stat(line, stats)
    case line
    when /can not run MPX/
      # processor lacks MPX XSTATE(s), can not run MPX tests
      @mpx_result = 'skip'
    when /^selftests.*: (.*) \[(PASS|FAIL|SKIP)\]/
      # selftests: mpx-mini-test_64 [PASS]
      @test_script = $1
      @result = $2
      if @test_script =~ /mpx-mini-test/ && @mpx_result
        # processor lacks MPX XSTATE(s), can not run MPX tests
        stats.add_test_result line, "#{@test}.#{@test_script}", @mpx_result
      else
        # selftests: mpx-mini-test_64 [PASS]
        stats.add_test_result line, "#{@test}.#{@test_script}", @result
      end
    when %r{: recipe for target.+failed}
      # Makefile:47: recipe for target 'sysret_ss_attrs_64' failed
      stats.add_test_result line, @test.to_s, 'make_fail' unless stats.key?(@test.to_s)
    else
      super(line, stats)
    end
  end
end

class FutexStater < Stater
end

class ResctrlStater < Stater
  def stat(line, stats)
    case line
    when /# Starting\s+(.*).../
      # Starting CAT test ...
      @test_script = $1
    when /# Pass: Check\s+(.*)/
      # Pass: Check MBM diff within 5%

      # The following line is repeated 3 times
      # Pass: Check resctrl mountpoint "/sys/fs/resctrl" exists
      stats.add_test_result line, "#{@test}.#{$1}", 'pass' unless stats.key? "#{@test}.#{$1}"
    when /# SKIP\s+(.*)/
      # The following line is repeated 3 times
      # ok 4 # SKIP Hardware does not support CAT or CAT is disabled
      stats.add_test_result line, "#{@test}.#{@test_script}", 'skip' unless stats.key? "#{@test}.#{@test_script}"
    when /^not ok\s+(.*)/
      # not ok CAT: cache miss rate within 4%
      @test_script = $1

      # not ok 3 CMT: test
      @test_script = $1.split(' ', 2)[1] if %w{1 2 3 4}.include? $1.split(' ', 2)[0]

      # The following line is repeated 3 times
      # not ok MBM: diff within 300%
      stats.add_test_result line, "#{@test}.#{@test_script}", 'fail' unless stats.key? "#{@test}.#{@test_script}"
    when /^ok\s+(.*)/
      # ok CAT: test
      # ok writing benchmark parameters to resctrl FS
      @test_script = $1

      # ok 2 MBA: schemata change
      @test_script = $1.split(' ', 2)[1] if %w{1 2 3 4}.include? $1.split(' ', 2)[0]

      # The following line is repeated 3 times
      # ok resctrl mountpoint "/sys/fs/resctrl" exists
      stats.add_test_result line, "#{@test}.#{@test_script}", 'pass' unless stats.key? "#{@test}.#{@test_script}"
    end
  end
end

class MptcpStater < Stater
  def stat(line, stats)
    case line
    when /^# (.*) ?\[ (OK|FAIL|SKIP| ok |fail) \]/
      # selftests: net/mptcp: diag.sh
      # no msk on netns creation                          [  ok  ]

      # for "ns1 MPTCP -> ns1 (10.0.1.1:10000      ) MPTCP (duration    75ms) [ OK ]"
      # it's @result is "OK"
      # it's @test_case is "ns1 MPTCP -> ns1 (10.0.1.1:10000      ) MPTCP"
      @result = $2
      @test_case = $1.gsub(/\(duration(.*)\)/, '')
      @test_case = @test_case.gsub(/[0-9]+\s+max\s+[0-9]+/, '')
      if @test_script == 'mptcp_connect.sh'
        # ns1 MPTCP -> ns1 (10.0.1.1:10000      ) MPTCP (duration    75ms) [ OK ]

        # to reduce below situation
        # ns2 MPTCP -> ns4 (dead:beef:3::1:10023) MPTCP copyfd_io_poll: poll timed out (events: POLLIN 1, POLLOUT 0)
        # (duration 30429ms) [ FAIL ] client exit code 0, server 2
        # but not exclude below line
        # setsockopt(..., TCP_ULP, "mptcp", ...) blocked  [ OK ]
        stats.add_test_result line, "#{@test_prefix}.#{@test_case}", @result if @test_case =~ /MPTCP|mptcp/
      else
        # defaults addr list                                 [ OK ]

        # ignore below duplication
        # msk in use statistics                             [  ok  ]
        # msk in use statistics                             [  ok  ]
        test_name = "#{@test_prefix}.#{@test_case}"
        stats.add_test_result line, test_name, @result unless stats.key?(test_name)
      end
    when /^# (PASS|FAIL|SKIP): (.*)/
      # selftests: net/mptcp: mptcp_sockopt.sh
      # PASS: all packets had packet mark set
      stats.add_test_result line, "#{@test_prefix}.#{$2}", $1 if @test_script == 'mptcp_sockopt.sh'
    else
      super(line, stats)
    end
  end
end

class LivepatchStater < Stater
  def stat(line, stats)
    case line
    when /^# TEST: (.*) \.\.\. (ok|fail|skip)/
      # TEST: multiple livepatches ... ok
      @result = $2
      @test_case = $1.gsub(/\(duration(.*)\)/, '')
      stats.add_test_result line, "#{@test_prefix}.#{@test_case}", @result
    else
      super(line, stats)
    end
  end
end

class TimensStater < Stater
  def stat(line, stats)
    case line
    when /# SKIP CLOCK_BOOTTIME_ALARM isn't supported/
      # ignore below skip, cause there's no test name when it skipped,
      # thus it's meaningless for auto-bisect
      # selftests: timens: timens
      # ok 3 # SKIP CLOCK_BOOTTIME_ALARM isn't supported
      # ok 4 # SKIP CLOCK_BOOTTIME_ALARM isn't supported

      # below shows their name when test passed,
      # can't unite the stats name between skip & pass
      # ok 3 Passed for CLOCK_BOOTTIME_ALARM (syscall)
      # ok 4 Passed for CLOCK_BOOTTIME_ALARM (vdso)

      # same as timerfd
      # selftests: timens: timerfd
      # ok 3 clockid=9

      # ok 3 # SKIP CLOCK_BOOTTIME_ALARM isn't supported
    when /# (ok|fail|skip) \d+ (.*):(.*)/
      # ok 1 clockid: 1 abs:0
      @test_case = "#{$2}:#{$3}"
      stats.add_test_result line, "#{@test_prefix}.#{@test_case}", $1
    when /# (ok|fail|skip) \d+ (.*)/
      # ok 1 Passed for CLOCK_BOOTTIME (syscall)
      stats.add_test_result line, "#{@test_prefix}.#{$2}", $1
    else
      super(line, stats)
    end
  end
end

class TimersStater < Stater
  def stat(line, stats)
    case line
    when /^# (.+\w)(\.\.\.)?\s+\[(OK|FAIL|SKIP|UNSUPPORTED)\]/,
         /^# ([^:]+\w)(\s?:.+)\[(OK|FAIL|SKIP|UNSUPPORTED)\]/
      # Check itimer virtual... [OK]
      # Nanosleep CLOCK_MONOTONIC                 [OK]
      # Mqueue latency :                          [OK]
      # Testing consistency with 8 threads for 30 seconds: [OK]
      # Estimating clock drift: 0.0(est) 0.0(act)     [OK]
      # CLOCK_TAI              RELTIME ONE-SHOT count:                   1 : [OK]
      stats.add_test_result line, "#{@test_prefix}.#{$1}", $3
    else
      super(line, stats)
    end
  end
end

class PstoreStater < Stater
  def stat(line, stats)
    case line
    when /^# (.*) \.\.\. (ok|fail|skip)/
      # Checking pstore backend is registered ... ok
      stats.add_test_result line, "#{@test_prefix}.#{$1}", $2
    else
      super(line, stats)
    end
  end
end

class DmaStater < Stater
  def stat(line, stats)
    case line
    when /^# average (map|unmap) latency\(us\):(.*) standard deviation:(.*)/
      # average unmap latency(us):0.6 standard deviation:1.1
      # average map latency(us):0.8 standard deviation:1.2
      @test_case = "average_#{$1}_latency"
      stats.add_metric "LAT.#{@test_prefix}.#{@test_case}", $2.to_f
      stats.add_metric "JIT.#{@test_prefix}.#{@test_case}_stddev", $3.to_f
    else
      super(line, stats)
    end
  end
end

class PidfdStater < Stater
  def stat(line, stats)
    case line
    when /# (ok|fail|skip) \d+ (.*)/
      # ok 1 global.wait_simple
      result = $1
      test_case = $2
      if test_case =~ /SKIP/
        # ok 8 # SKIP pidfd_send_signal signal recycled pid test: Skipping test
        # ksft_test_result_skip("%s test: Skipping test\n", test_name);
        test_case = test_case.split(' SKIP ').last.split(' test: Skipping').first
        result = 'skip'
      end

      if @test_script == 'pidfd_test'
        # pidfd_test will run twice subtest and output the result twice,
        # ignore the duplication of line 30, line 32771 and line 32776.
        # selftests: pidfd: pidfd_test
        # 25 # ok 1 pidfd_poll check for premature notification on child thread exec test: Passed
        # 30 # ok 2 pidfd_poll check for premature notification on child thread exec test: Passed
        # 32771 # ok 1 pidfd_poll check for premature notification on child thread exec test: Passed
        # 32776 # ok 2 pidfd_poll check for premature notification on child thread exec test: Passed
        stats.add_test_result line, "#{@test_prefix}.#{test_case}", result unless stats.key? "#{@test_prefix}.#{test_case}"
      else
        stats.add_test_result line, "#{@test_prefix}.#{test_case}", result
      end
    else
      super(line, stats)
    end
  end
end

class SgxStater < Stater
  def stat(line, stats)
    case line
    when /RUN\s+([^\s]+)/
      # #  RUN           enclave.augment_via_eaccept ...
      @test_case = $1
    when /^# (ok|not ok) \d+ ([^#].*)/
      # not ok 1 enclave.unclobbered_vdso
      if $2 != @test_case
        log_warn "#{$2} not equals to #{@test_case}"
        exit 1
      end

      stats.add_test_result line, "#{@test_prefix}.#{$2}", $1
      @test_case = nil
    when /^# ok \d+ # SKIP (.*)/
      # ok 3 # SKIP System does not support SGX2
      stats.add_test_result line, "#{@test_prefix}.#{@test_case}", 'skip'
      @test_case = nil
    else
      # ok 1 selftests: sgx: test_sgx
      super(line, stats)
    end
  end
end

class Clone3Stater < Stater
  def stat(line, stats)
    case line
    when /Trying (.*)/
      # # [1246] Trying clone3() with flags 0x20000000 (size 0)
      @test_case = $1
    when /^# (ok|not ok) \d+ \[[0-9]+\] (.*)/
      # ok 2 [1246] Result (0) matches expectation (0)

      # below 'ok 6' is the same as 'ok 7', thus ignore the duplication
      # # [1246] Trying clone3() with flags 0 (size 0)
      # # Invalid argument - Failed to create new process
      # # [1246] clone3() with flags says: -22 expected -22
      # ok 6 [1246] Result (-22) matches expectation (-22)
      # # [1246] Trying clone3() with flags 0 (size 0)
      # # Invalid argument - Failed to create new process
      # # [1246] clone3() with flags says: -22 expected -22
      # ok 7 [1246] Result (-22) matches expectation (-22)
      stats.add_test_result line, "#{@test_prefix}.#{@test_case}.#{$2}", $1 unless stats.key? "#{@test_prefix}.#{@test_case}.#{$2}"
    else
      super(line, stats)
    end
  end
end

class DmabufStater < Stater
  def stat(line, stats)
    case line
    when /^#   (Testing .*):  (OK|FAIL)/
      #   Testing allocation and importing:   OK
      stats.add_test_result line, "#{@test_prefix}.#{$1}", $2
    else
      super(line, stats)
    end
  end
end

class FirmwareStater < Stater
  def stat(line, stats)
    case line
    when /^# Running kernel configuration test \d+ -- (.*)/
      # Running kernel configuration test 1 -- rare
      @test_case = $1
    when /^# Testing with the (file .*)\.\.\.$/
      # Testing with the file missing...
      @test_subcase = $1
    when /^# (.*): ?(PASS|OK|FAIL|SKIP|Pass|Fail|Skip)/
      # Batched request_firmware_into_buf() nofile try #1: OK
      stats.add_test_result line, "#{@test_prefix}.#{@test_case}.#{@test_subcase}.#{$1}", $2
    else
      super(line, stats)
    end
  end
end

class CapabilitiesStater < Stater
  def stat(line, stats)
    case line
    when /\[RUN\].*(Tests with uid .*) \+\+\+/
      # [RUN] +++ Tests with uid == 0 +++
      # # [RUN] +++ Tests with uid != 0 +++
      @test_case = $1
    when /^Pass (\d+) Fail (\d+) Xfail (\d+) Xpass (\d+) Skip (\d+) Error (\d+)/,
         /^# # Totals: pass:(\d+) fail:(\d+) xfail:(\d+) xpass:(\d+) skip:(\d+) error:(\d+)/
      # Pass 9 Fail 0 Xfail 0 Xpass 0 Skip 0 Error 0
      # # Totals: pass:9 fail:0 xfail:0 xpass:0 skip:0 error:0
      @result = 'skip'
      @result = 'fail' if $2 != '0' || $3 != '0' || $6 != '0'
      @result = 'pass' if $2 == '0' && $3 == '0' && $6 == '0'
      stats.add_test_result line, "#{@test_prefix}.#{@test_case}", @result
    else
      super(line, stats)
    end
  end
end

class AndroidStater < Stater
  def stat(line, stats)
    case line
    when /^(ion_test.sh: .*) - \[(PASS|FAIL|SKIP)\]$/
      # ion_test.sh: heap_type: 0 - [FAIL]
      stats.add_test_result line, "#{@test_prefix}.#{$1}", $2
    else
      super(line, stats)
    end
  end
end

class BreakpointsStater < Stater
  def stat(line, stats)
    case line
    when /(ok|fail|skip) \d+ (Test .*)/
      # ok 1 Test breakpoint 0 with local: 0 global: 1
      stats.add_test_result line, "#{@test_prefix}.#{$2}", $1
    when /No such collection '(breakpoints)'/
      # No such collection 'breakpoints'
      stats.add_test_result line, $1.to_s, 'fail'
    else
      super(line, stats)
    end
  end
end

class Ia64Stater < Stater
  def stat(line, stats)
    case line
    when /^(# )?(PASS|FAIL|SKIP): (.*)/
      # PASS: /dev/mem 0xc0000-0x100000 is readable
      # # PASS: /dev/mem 0x0-0xa0000 is readable
      stats.add_test_result line, "#{@test_prefix}.#{$3}", $2
    else
      super(line, stats)
    end
  end
end

class KmodStater < Stater
  def stat(line, stats)
    case line
    when /^# Running test: (kmod_test.*) - run/
      # below a test may run several times, regard them as one test
      # Running test: kmod_test_0005 - run #1
      # kmod_test_0005: OK! - loading kmod test
      # kmod_test_0005: OK! - Return value: 0 (SUCCESS), expected SUCCESS
      # Tue Sep 15 17:57:54 UTC 2020
      # Running test: kmod_test_0005 - run #2
      # kmod_test_0005: OK! - loading kmod test
      # kmod_test_0005: OK! - Return value: 0 (SUCCESS), expected SUCCESS
      @last_test_case = @test_case
      @test_case = $1 unless $1.nil?

      if @test_case != @last_test_case && !@last_test_case.nil?
        # regard whole subtest as 'pass' if @test_case is 'OK' or 'SKIP'
        @test_case_result = 'pass' if @test_case_result != 'FAIL' && !@all_test_case_skip
        stats.add_test_result line, "#{@test_prefix}.#{@last_test_case}", @test_case_result
        # reset @test_case_result and all_subtest_case_skip for new test_case
        @test_case_result = nil
        @all_test_case_skip = true
      end
    when /^# (kmod_test.*|kmod_check_visibility): (OK|FAIL|SKIP)/
      # if any single test fails, regard the whole subtest fail
      @test_case_result = $2 if @test_case_result != 'FAIL'
      # when all @test_case_result are 'SKIP', regard the subtest as 'skip'
      @all_test_case_skip = @test_case_result == 'SKIP' if @all_test_case_skip
    when /^# Test completed/
      # '# Test completed' marks the whole kmod tests finished
      stats.add_test_result line, "#{@test_prefix}.#{@test_case}", @test_case_result
    else
      super(line, stats)
    end
  end
end

class NetfilterStater < Stater
  def stat(line, stats)
    case line
    when /^# TEST: (.*)/
      # selftests: netfilter: nft_concat_range.sh
      # TEST: reported issues
      @test_case = $1 if @test_script == 'nft_concat_range.sh'
    when /^#   (.+)\[( OK|FAIL|SKIP)/
      #   Add two elements, flush, re-add                               [ OK ]
      @subtest_case = $1.strip if @test_script == 'nft_concat_range.sh'
      stats.add_test_result line, "#{@test_prefix}.#{@test_case}.#{$1}", $2 if @test_script == 'nft_concat_range.sh'
    when /^#     (baseline .+|set with .+):\s*(\S*)pps/
      #     baseline (drop from netdev hook):               1612678pps
      stats.add_metric "RATE.#{@test_prefix}.#{@test_case}.#{@subtest_case}.#{$1}.packets_per_sec", $2.to_i if @test_script == 'nft_concat_range.sh'
    when /^# (PASS|FAIL|SKIP): (ns\d)-\S+ (connection on port.*)/
      # selftests: netfilter: nft_conntrack_helper.sh
      # PASS: ns1-Ip028vuy connection on port 2121 has ftp helper attached
      # PASS: ns2-Ip028vuy connection on port 2121 has ftp helper attached
      # PASS: ns1-Ip028vuy connection on port 2121 has ftp helper attached
      # PASS: ns2-Ip028vuy connection on port 2121 has ftp helper attached
      stats.add_test_result line, "#{@test_prefix}.#{$2}_#{$3}", $1 unless stats.key? "#{@test_prefix}.#{$2}_#{$3}"
    when /^# (PASS|FAIL|SKIP|OK): (.*)/
      # PASS: ipsec tunnel mode for ns1/ns2

      # 46 # selftests: netfilter: nft_flowtable.sh
      # 52 # FAIL: file mismatch for ns1 -> ns2
      # 78 # FAIL: file mismatch for ns1 -> ns2

      result = $1
      test_case = $2

      # selftests: netfilter: nft_queue.sh
      # PASS: ns1-IpVmXxqi can reach ns2-IpVmXxqi
      # rm -IpVmXxqi
      test_case = test_case.gsub($1, '') if test_case =~ /ns\d+(-\w+)/

      test_name = "#{@test_prefix}.#{test_case}"
      stats.add_test_result line, test_name, result unless stats.key?(test_name)
    else
      super(line, stats)
    end
  end
end

class ExecStater < Stater
  def stat(line, stats)
    case line
    when /^(Check .*)... \[(OK|FAIL|SKIP)\]/
      # Check success of execveat(8, 'execveat', 0)... [OK]
      subname = "#{@test_prefix}.#{$1}"
      res = $2

      # ignore detail stats of execveat to avoid duplication
      stats.add_test_result line, subname, res unless @test_script =~ /execveat/
    when /: recipe for target.+failed$/, /^make: \*\*\* (.*) (Error \d+|Stop\.)$/
      # make: *** No rule to make target .*, needed by 'all'.  Stop.
      stats.add_test_result line, @test.to_s, 'make_fail'
    when /^# (ok|not ok) ([0-9]+) (.*)/
      test_case = $3.split('- ').last
      # selftests: exec: null-argv
      # ok 1 execve(argv[0], str, NULL)

      # selftests: exec: binfmt_script.py
      # ok 1 - binfmt_script too-big (correctly failed bad exec)

      # ignore below duplication
      # ok 20 - binfmt_script two-under-no-nl (successful good exec)
      # ok 24 - binfmt_script two-under-no-nl (successful good exec)
      stats.add_test_result line, "#{@test_prefix}.#{test_case}", $1 unless stats.key? "#{@test_prefix}.#{test_case}"
    else
      super(line, stats)
    end
  end
end

class MqueueStater < Stater
  attr_accessor :mqueue_speed

  def initialize(test, test_script)
    super(test, test_script)
    @mqueue_speed = {}
  end

  def stat(line, stats)
    case line
    when /^# (.*):.*(PASS|FAIL|SKIP)/
      # Queue open with mq_maxmsg > limit when euid = 0 succeeded:            PASS
      stats.add_test_result line, "#{@test_prefix}.#{$1}", $2
    when /Test #([1-9].*):/
      # Test #2b: Time send/recv message, queue full, increasing prio
      @mqueue_test = Regexp.last_match[1]
    when /(Send|Recv) msg:/
      #  Send msg:                       0.48443412s total time
      @io = Regexp.last_match[1]
    when %r{(\d+) nsec/msg}
      # 484 nsec/msg
      @mqueue_speed["LAT.#{@mqueue_test}.#{@io}"] = Regexp.last_match[1].to_i
    when /make: Leaving.*mqueue'/
      stats.add_metric "LAT.#{@test}.nsec_per_msg", @mqueue_speed.values.average.to_i unless @mqueue_speed.empty?
    else
      super(line, stats)
    end
  end
end

class PrctlStater < Stater
  def stat(line, stats)
    case line
    when /^(ok|not ok) (\d+) selftests: (\S*): (\S*)/
      # selftests: prctl: disable-tsc-ctxt-sw-stress-test
      # [No further output means we're allright]
      # ok 1 selftests: prctl: disable-tsc-ctxt-sw-stress-test
      # selftests: prctl: disable-tsc-ctxt-sw-stress-test
      # [No further output means we're allright]
      # ok 2 selftests: prctl: disable-tsc-ctxt-sw-stress-test
      stats.add_test_result line, "#{@test}.#{$2}.#{$4}", $1
    end
  end
end

class BpfStater < Stater
  def stat(line, stats)
    case line
    when /^(#| ) #(\d+|\d+\/\d+)\s+([^:]+):(OK|FAIL|SKIP)/
      # #1/11 variable subtraction:OK
      # # #1/12 pointer variable subtraction:OK
      # # #1 align:OK
      # # #2 atomic_bounds:OK
      # # #3/1 add:OK
      # # #3/2 sub:OK
      # # #3/3 and:OK
      r = $4 == 'OK' ? 'ok' : 'not ok'
      pattern1 = if $3.include? '/'
                   # selftests: bpf: test_progs
                   # #1/1     align/mov:OK
                   # #1/2     align/shift:OK
                   $3.split('/')[1]
                 else
                   # selftests: bpf: test_progs
                   # #1/1 mov:OK
                   # #1/2 shift:OK
                   $3
                 end
      subname = "#{@test_prefix}.#{pattern1.gsub(' ', '_')}"
      stats.add_test_result line, subname, r unless stats.key? subname
    when /^# selftests:\s(.*)\s\[(PASS|SKIP|FAIL)/
      # echo "selftests: test_xdp_redirect $xdpmode [FAILED]";
      # echo "selftests: test_xdp_redirect $xdpmode [SKIP]"
      # echo "selftests: test_xdp_redirect $xdpmode [PASS]";
      stats.add_test_result line, "#{@test_prefix}.#{$1}", $2
    when /^# (fail|Pass):\s(.*)/
      # selftests: bpf: test_xdp_redirect_multi.sh
      # fail: xdpgeneric arp(F_BROADCAST) ns1-1
      # Pass: xdpgeneric IPv6 (no flags) ns1-2
      stats.add_test_result line, "#{@test_prefix}.#{$2}", $1
    when /^# Test case:\s(.*)\s\.\.\s\[(PASS|FAIL)\]/
      # selftests: bpf: test_sysctl
      # Test case: sysctl wrong attach_type .. [PASS]

      # printf("[%s]\n", err ? "FAIL" : "PASS");
      stats.add_test_result line, "#{@test_prefix}.#{$1}", $2
    when /^# #([0-9]+)\/([up])\s(.*)\s(SKIP|OK)/
      # selftests: bpf: test_verifier
      # #0/u invalid and of negative number SKIP
      # #0/p invalid and of negative number OK
      stats.add_test_result line, "#{@test_prefix}.#{$1}/#{$2}.#{$3}", $4
    when /^# main:(PASS|FAIL):(.*)/
      # selftests: bpf: get_cgroup_id_user
      # main:PASS:cgroup_setup_and_join
      # main:PASS:bpf_find_map
      # main:PASS:bpf_find_map
      stats.add_test_result line, "#{@test_prefix}.#{$2}", $1 unless stats.key? "#{@test_prefix}.#{$2}"
    when /^# (test_lru_sanity\d+)\s(\(.*\)): (Pass)/
      # selftests: bpf: test_lru_map
      # test_lru_sanity0 (map_type:9 map_flags:0x0): Pass
      stats.add_test_result line, "#{@test_prefix}.#{$1}_#{$2}", $3
    when /^# starting (egress|ingress)\s(.*)/
      # selftests: bpf: test_lwt_ip_encap.sh
      # starting egress IPv4 encap test
      # PASS
      # starting egress IPv6 encap test
      #     test_ping failed: expected: 0; got 1
      # FAIL
      @test_case = "#{$1}_#{$2}"
    when /^# (PASS|FAIL)$/
      stats.add_test_result line, "#{@test_prefix}.#{@test_case}", $1 if @test_case
    else
      # not ok 6 selftests: bpf: test_progs # exit=1
      super(line, stats)
    end
  end
end

class FtraceStater < Stater
  def stat(line, stats)
    case line
      # # [1] Basic trace file check    [PASS]
      # # [2] Basic test for tracers    [PASS]
      # # [3] Basic trace clock test    [PASS]
      # # [4] Basic event tracing check [PASS]
    when /^# \[\d+\] (.*)\[(.*)\]/
      pattern1 = $1
      result = $2
      subname = "ftrace.#{pattern1.gsub(' ', '_')}"
      stats.add_test_result line, subname, result unless stats.key? subname
    end
  end
end

class TCTestingStater < Stater
  def stat(line, stats)
    case line
      # not ok 10 ce7d - Add mq Qdisc to multi-queue device (4 queues)
      # ok 46 1298 - Add duplicate bfifo qdisc on egrese
      # ok 47 45a0 - Delete nonexistent bfifo qdisc
      #       Could not match regex pattern. Verify command output:
    when /^# (ok|not ok) (\d+) (.*) - (.*)/
      result = $1
      # e7d|1298|45a0 is unique for each sub-test
      subname = "tc-testing.#{$3}.#{$4.gsub(' ', '_')}"
      stats.add_test_result line, subname, result
    end
  end
end

class VmallocStater < Stater
  attr_accessor :tmp_stats

  def initialize(test, test_script)
    super(test, test_script)
    @tmp_stats = {}
  end

  def stat(line, stats)
    case line
    when /(Check the kernel ring buffer to see the summary|Ccheck the kernel message buffer to see the summary)/
      stats.add_test_result line, @test_prefix.to_s, 'pass'
    # vmalloc test stat: (stress test worker number = nr_threads)
    # [  223.093027] Summary: fix_size_alloc_test passed: 1 failed: 0 repeat: 1 loops: 1000000 avg: 5861476 usec
    # [  223.103232] Summary: full_fit_alloc_test passed: 1 failed: 0 repeat: 1 loops: 1000000 avg: 5878630 usec
    # [  223.113422] Summary: long_busy_list_alloc_test passed: 1 failed: 0 repeat: 1 loops: 1000000 avg: 60293287 usec
    # [  223.124222] Summary: random_size_alloc_test passed: 1 failed: 0 repeat: 1 loops: 1000000 avg: 31075131 usec
    # [  223.134778] Summary: fix_align_alloc_test passed: 1 failed: 0 repeat: 1 loops: 1000000 avg: 7383628 usec
    # [  223.145064] Summary: random_size_align_alloc_test passed: 1 failed: 0 repeat: 1 loops: 1000000 avg: 9595644 usec
    # [  223.156057] Summary: align_shift_alloc_test passed: 0 failed: 1 repeat: 1 loops: 1000000 avg: 402157 usec
    # [  223.166406] Summary: pcpu_alloc_test passed: 1 failed: 0 repeat: 1 loops: 1000000 avg: 278274 usec
    # [  223.176299] Summary: kvfree_rcu_1_arg_vmalloc_test passed: 1 failed: 0 repeat: 1 loops: 1000000 avg: 6248522 usec
    # [  223.187358] Summary: kvfree_rcu_2_arg_vmalloc_test passed: 1 failed: 0 repeat: 1 loops: 1000000 avg: 6333619 usec
    # [  223.198414] All test took worker0=441656368064 cycles
    when /Summary: (.+) passed: (.+) failed: (.+) repeat: (.+) loops: (.+) avg: (.+) usec/
      if $3.to_i.positive?
        stats.add_test_result line, @test_prefix + '.' + $1, 'fail'
      elsif $2.to_i.positive?
        stats.add_test_result line, @test_prefix + '.' + $1, 'pass'
        stats.append_metric "LAT.#{@test_prefix}.#{$1}.usec_per_loop", $6.to_i
      end
    end
  end
end

class ProtectionKeysStater < Stater
  def stat(line, stats)
    case line
    when /done \(all tests OK\)/
      stats.add_test_result line, @test_prefix.to_s, 'pass'
    when /protection_keys\.c:.* Assertion /, /assert\(\) at protection_keys.c:/
      stats.add_test_result line, @test_prefix.to_s, 'fail'
    when /running PKEY tests for unsupported CPU\/OS/
      stats.add_test_result line, @test_prefix.to_s, 'skip'
    end
  end
end

class SyncStater < Stater
  def stat(line, stats)
    case line
    when /^# (ok|not ok) \d+ \[RUN\]\s+(.*)/
      # ok 1 [RUN]    test_alloc_timeline
      stats.add_test_result line, "#{@test_prefix}.#{$2}", $1
    else
      # ok 1 selftests: sync: sync_test
      super(line, stats)
    end
  end
end

class SysctlStater < Stater
  def stat(line, stats)
    case line
    when /^# (Checking production write strict setting) ... (ok|not ok)/
      # Checking production write strict setting ... ok
      stats.add_test_result line, "#{@test_prefix}.#{$1}", $2
    when /^# Running test: (sysctl_test_[0-9]+) - run #([0-9]+)/
      # Running test: sysctl_test_0001 - run #0
      @test_case = "#{$1}.#{$2}"
    when /^# ([^.]*)\s*...\s*(ok|not ok)/
      # Checking bitmap handler... ok
      test_name = "#{@test_prefix}.#{@test_case}.#{$1}"

      # ignore duplication
      # 1609 # Running test: sysctl_test_0002 - run #0
      # 1869 # Running test: sysctl_test_0002 - run #0
      stats.add_test_result line, test_name, $2 unless stats.key?(test_name)
    else
      # ok 1 selftests: sysctl: sysctl.sh
      super(line, stats)
    end
  end
end

while (line = $stdin.gets)
  line = line.resolve_invalid_bytes

  case line
  when /^# selftests: (net): (.*)/
    # selftests: net: reuseport_addr_any.sh
    stater = NetStater.new($1, $2)
  when /^# selftests: vm: (protection_keys.*)/
    stater = ProtectionKeysStater.new('vm', $1)
  when /^# selftests: (vm): (.+)/
    # selftests: vm: run_vmtests
    stater = VmStater.new($1, $2)
  when /^# selftests: memory-hotplug: (.*\.sh)/,
    # selftests: memory-hotplug: mem-on-off-test.sh
       /\.\/(.*\.sh).*memory-hotplug/
    # ./mem-on-off-test.sh -r 2 || echo "selftests: memory-hotplug [FAIL]"
    stater = MemoryHotplugStater.new('memory-hotplug', $1)
  when /gcc -Wall -O2 (.*).c -o/
    # gcc -Wall -O2 unprivileged-remount-test.c -o unprivileged-remount-test
    stater = MountStater.new('mount', $1)
  when /^# selftests: (x86): (.+)/
    # selftests: x86: single_step_syscall_32
    stater = X86Stater.new($1, $2)
  when /^make: Entering.*(x86)'/
    # for mpx.skip
    # processor lacks MPX XSTATE(s), can not run MPX tests
    stater = X86Stater.new($1, nil)
  when /^# selftests: (futex): (.+)/
    # selftests: futex: run.sh
    stater = FutexStater.new($1, $2)
  when /kernel supports (resctrl) filesystem/
    # Pass: Check kernel supports resctrl filesystem
    # ok kernel supports resctrl filesystem
    stater = ResctrlStater.new($1, nil)
  when /^# selftests: (net\/mptcp): (.*\.sh)/
    # selftests: net/mptcp: mptcp_connect.sh
    stater = MptcpStater.new($1, $2)
  when /^# selftests: (livepatch): (.*)/
    # selftests: livepatch: test-livepatch.sh
    stater = LivepatchStater.new($1, $2)
  when /# selftests: (timens): (.*)/
    # selftests: timens: timens
    stater = TimensStater.new($1, $2)
  when /# selftests: (timers): (.*)/
    # selftests: timers: posix_timers
    stater = TimersStater.new($1, $2)
  when /# selftests: (pstore): (.*)/
    # selftests: pstore: pstore_tests
    stater = PstoreStater.new($1, $2)
  when /# selftests: (dma): (.*)/
    # selftests: dma: dma_map_benchmark
    stater = DmaStater.new($1, $2)
  when /# selftests: (pidfd): (.*)/
    # selftests: pidfd: pidfd_poll_test
    stater = PidfdStater.new($1, $2)
  when /# selftests: (firmware): (.*)/
    # selftests: firmware: fw_run_test.sh
    stater = FirmwareStater.new($1, $2)
  when /(^|^# )selftests: (capabilities): (.*)/
    # selftests: capabilities: test_execve
    stater = CapabilitiesStater.new($2, $3)
  when /^selftests: (android): (.*)/
    # selftests: android: run.sh
    stater = AndroidStater.new($1, $2)
  when /make run_tests -C (android)/
    # for below situation:
    # not ok 1 selftests: android: run.sh # SKIP
    stater = AndroidStater.new($1, 'run.sh')
  when /^(# |)selftests: (breakpoints): (.*)/
    # selftests: breakpoints: breakpoint_test
    stater = BreakpointsStater.new($2, $3)
  when /run_kselftest\.sh -c (breakpoints)/
    # for below situation:
    # No such collection 'breakpoints'
    stater = BreakpointsStater.new($1, nil)
  when /(^|^# )selftests: (ia64): (.*)/
    # selftests: ia64: aliasing-test
    stater = Ia64Stater.new($2, $3)
  when /^# selftests: (kmod): (.*)/
    # selftests: kmod: kmod.sh
    stater = KmodStater.new($1, $2)
  when /^# selftests: (netfilter): (.*)/
    # selftests: netfilter: nft_queue.sh
    stater = NetfilterStater.new($1, $2)
  when /(^|^# )selftests: (exec): (.*)/
    # selftests: exec: execveat
    stater = ExecStater.new($2, $3)
  when /^# selftests: (mqueue): (.*)/
    # selftests: mqueue: mq_perf_tests
    stater = MqueueStater.new($1, $2)
  when /^# selftests: (prctl): (.*)/
    # selftests: prctl: disable-tsc-ctxt-sw-stress-test
    stater = PrctlStater.new($1, $2)
  when /^# selftests: (bpf): (.*)/
    # selftests: bpf: test_progs
    stater = BpfStater.new($1, $2)
  when /^# selftests: (ftrace): (ftracetest)$/
    # selftests: ftrace: ftracetest
    stater = FtraceStater.new($1, $2)
  when /^# selftests: (tc-testing): (tdc.sh)$/
    # selftests: ftrace: ftracetest
    stater = TCTestingStater.new($1, $2)
  when /^# selftests: (sgx): (.*)$/
    # selftests: sgx: test_sgx
    stater = SgxStater.new($1, $2)
  when /^# selftests: (clone3): (.*)$/
    # selftests: clone3: clone3
    stater = Clone3Stater.new($1, $2)
  when /^# selftests: (dmabuf-heaps): (.*)$/
    # selftests: dmabuf-heaps: dmabuf-heap
    stater = DmabufStater.new($1, $2)
  when /# selftests: (sync): (.*)/
    # selftests: sync: sync_test
    stater = SyncStater.new($1, $2)
  when /# selftests: (sysctl): (.*)/
    # selftests: sysctl: sysctl.sh
    stater = SysctlStater.new($1, $2)
  when /^LKP SKIP (.*)/
    # LKP SKIP net.l2tp.sh
    # add "duplicate key" check to avoid below duplication
    # 1718 LKP SKIP USERCOPY_STACK_FRAME_TO
    # 2884 LKP SKIP USERCOPY_STACK_FRAME_TO
    # 9853 LKP SKIP USERCOPY_STACK_FRAME_TO
    stats.add_test_result line, $1.to_s, 'skip' unless stats.key? $1.to_s
  when /^# selftests: (.+): (.+)/
    stater = Stater.new($1, $2)
  when %r{make: Entering directory .*/(.*)'}
    stater = Stater.new($1, nil)
  when /vm\/test_vmalloc.sh (stress|performance)/
    stater = VmallocStater.new('vm.test_vmalloc.sh', $1)
  else
    if stater
      stater.stat(line, stats)
      next
    end
  end
end

stats.dump
